おきらくPerlプログラミング入門
           ~~めざせ Perl マスター~~

                広井 誠

                第15回



○オブジェクト指向(その1)

 今回から、いよいよ「オブジェクト指向」の話に入ります。プログラミングに

興味のある方ならば、オブジェクト指向という言葉は聞いたことがあると思いま

す。よく使われているオブジェクト指向言語にC++や Java があります。C++は

オブジェクト指向プログラミングができるようにC言語を拡張したものですが、

度重なる機能追加により複雑な言語仕様になってしまいました。このため、初心

者がオブジェクト指向を学ぶには適していない、と言われています。その点、

Java は初めからオブジェクト指向言語として設計されたため、すっきりとした

言語仕様を持っています。オブジェクト指向だけではなく、プログラミング入門

用の言語として推薦するユーザーも多いようです。


 Perl のオブジェクト指向はバージョン 5 で追加された機能ですが、C++のよ

うな複雑なものではありません。一般のオブジェクト指向言語とはちょっと変わっ

ていますが、Perl らしい実装方法といえるでしょう。まずはオブジェクト指向

について簡単に説明します。


○オブジェクト指向とは?

 第5回で簡単に説明しましたが、プログラミングは模型を組み立てる作業と似

ています。模型が大きくなると、一度に全体を組み立てるのは難しくなります。

そのような場合、全体をいくつかに分割して、まずその部分ごとに作ります。最

後に、それを結合して全体を完成させます。これは模型に限らず、あらゆる分野

で使われている手法[*1]です。

 これは、プログラミングにも当てはまります。実現しようとする処理が複雑に

なると、一度に全部作ることは難しくなります。そこで、全体を小さな処理に分

割して、個々の処理を作成します。それらを組み合わせて全体のプログラムを完

成させるのです。

 今までのプログラミングでは、この部品に相当するのが「関数」です。関数の

役割は、入力されたデータを処理してその結果を返すことです。つまり、関数は

機能を表しているので、関数を部品とは見なすには少々無理があるのです。この

ため、全体を小さな処理に分割するにしても、機能単位で行われることが普通で

す。

 オブジェクト指向プログラミングでは、関数ではなく「オブジェクト(object:

物体)」を部品として扱います。私達の周囲にはいろいろな「モノ」があります

が、プログラムでいうオブジェクトとは何でしょう。たとえば、えんぴつを考え

てみます。えんぴつには、色、長さ、固さ、などいろいろな性質がありますね。

そして、えんぴつを使って紙に文字を書いたり、絵を描いたりすることができま

す。普通のえんぴつを使えば黒い文字を、赤えんぴつを使えば赤い文字を書くこ

とができます。プログラムでは、このような性質をデータで表し、機能を関数で

表すことになります。そしてオブジェクトとは、このデータと関数を結び付けた

ものなのです。

 今までのプログラミング言語では、データと関数を別々に定義するため、それ

を一つのオブジェクトとして表すことができません。赤えんぴつで赤い文字を書

くにも、えんぴつの種類をチェックして赤い文字を書くようにプログラムしなけ

ればいけません。ところが、オブジェクトはデータと関数を結び付けたものなの

で、自分が何をしたらよいかわかっています。えんぴつオブジェクトに文字を書

けと命じれば、それが赤えんぴつのオブジェクトであれば文字は赤に、黒えんぴ

つのオブジェクトであれば黒い文字となるのです。

 このように、オブジェクトはデータと関数を一つにまとめたものです。従来の

プログラミングが全体を機能単位で分割するのに対し、オブジェクト指向プログ

ラミングでは全体をオブジェクト単位に分割して、それを組み合わせることでプ

ログラムを作っていきます。また、オブジェクトは現実の「モノ」を比較的容易

に表すことができるので、人間にとって理解しやすいプログラムになります。

 ところで、データと関数を結び付けることは、従来のプログラミング言語でも

可能です。オブジェクト指向はプログラミングの考え方のひとつであり、C++の

ようなオブジェクト指向言語を使わなくても、たとえば、C言語でもマシン語で

も、その考え方にしたがってプログラムを作れば、オブジェクト指向プログラミ

ングができます。実際、オブジェクト指向には様々な考え方があり、いろいろな

オブジェクト指向言語が存在します。ですが、データと関数を一つにまとめたも

のをオブジェクトとして扱う、という基本的な考え方は、オブジェクト指向言語

の元祖と言われる Smalltalk でも、C++、Java、Perl でも同じです。


note:

 [*1] 分割統治法といいます。


○クラス、インスタンス、メソッド、継承

 次に、一般のオブジェクト指向でよく使われる、クラス、インスタンス、メソ

ッド、継承について簡単に説明します。

 「クラス(class)」はオブジェクトの振る舞いを定義したものです。ここでデー

タを格納するための変数や、それを操作する関数が定義されます。クラスはオブ

ジェクトの設計図にあたるもので、オブジェクトの「雛形」と呼ぶこともありま

す。クラスはオブジェクトの振る舞いを定義するだけで、アクセスできる実体は

何も生み出していない、ということに注意してください。このクラスから実体と

して作り出されるのが「インスタンス(instance)」です。このインスタンスを

「オブジェクト」と考えてください。インスタンスを生成する方法は、当然です

がプログラミング言語によって違います。C++や Java は new を使いますが、

CLOS (Common Lisp Object System) や拙作の Lisp インタプリタ VTOL では

make-instance を使います。

 くどいようですが、「クラス」はオブジェクトの振る舞いを定義するだけで、

実際にアクセスするオブジェクトが「インスタンス」である、ということに注意

してくださいね。次の図を見てください。


    ┌─ class Foo ─┐            ┌─ instance ─┐
    │        │            │       │
    │   設計図   │─ インスタンスの生成 →│  実体    │
    │        │            │       │
    └────────┘            └───────┘
         │
         │
         │                ┌─ instance ─┐
         │                │       │
         └───── インスタンスの生成 →│  実体    │
                          │       │
                          └───────┘

          図 1 : クラスとインスタンスの関係


 クラスはオブジェクトの定義を表すものですから、Foo というクラスは1つし

かありません。これに対し、インスタンスはクラスから生み出されるオブジェク

トです。クラス Foo に new や make-instance を適用することで、いくつでも

インスタンスを生み出すことができるのです。クラスは設計図であり、それに従

って作られるオブジェクトがインスタンス、それを作り出す工場が make-instance

や new である、と考えるとわかりやすいでしょう。

 「メソッド(method)」はオブジェクトと結び付けた関数です。オブジェクト指

向プログラミングでは、他の関数から直接オブジェクトを操作することはせず、

メソッドを呼び出すことで行います。

 メソッドは、クラスが異なっていれば同じ名前のメソッドを定義することがで

きます。たとえば、クラス Foo1 にメソッド bar が定義されていても、クラス

Foo2 に同名のメソッド bar を定義することができます。そして、ここからが重

要なのですが、あるオブジェクトに対してメソッド bar を呼び出した場合、そ

れが Foo1 から作られたオブジェクトであれば、Foo1 で定義された bar が実行

され、Foo2 から作られたオブジェクトであれば、Foo2 で定義された bar が実

行されるのです。このように、オブジェクトが属するクラスによって、実行され

るメソッドが異なるのです。これを「ポリモーフィズム(polymorphism)」と呼び

ます。この機能により、オブジェクトは自分が行うべき適切な処理を実行できる

わけです。

 クラス、インスタンス、メソッドの関係を図に示すと、次のようになります。


    ┌─ class Foo1 ─┐           ┌─ instance ─┐
    │        │           │       │
    │  設計図    │──  生成  ──→│  実体    │
    │        │           │       │
    └────────┘           └───────┘
         │                    ↑
         │定義                  │
         ↓                    │Write
    ┌── method ──┐               │
    │        │               │
    │   bar    │←──────────────┘
    │        │ Read
    └────────┘

        図 2 : クラス、インスタンス、メソッドの関係


 クラスという設計図が中心にあり、そこからインスタンスが生み出され、メソ

ッドを使ってインスタンスを操作する、という関係になります。


 「継承(inheritance:インヘリタンス)」はオブジェクト指向の目玉ともいえる機能で、

簡単に言うとクラスに「親子関係」を持たせる機能です。子供のクラスは親クラ

スの性質を受け継ぐことができます。プログラミング言語の場合、引き継ぐ性質

は定義されたデータやメソッドとなります。プログラムを作る場合、今まで作っ

たプログラムと同じような機能が必要になることが多いのですが、継承を使うこ

とでその機能を受け継ぎ、新規の機能や変更される機能だけプログラムする、い

わゆる「差分プログラミング」が可能となります。

 あるクラスを継承する場合、その元になるクラスを「スーパークラス」とか

「親クラス」、「ベースクラス」と呼びます。そして、継承したクラスを「サブ

クラス」と呼びます。この呼び方は言語によってまちまちで統一されていません。

C++の場合は、元になるクラスを基本クラスといい、継承するクラスを派生クラ

スとか導出クラスといいます。

 たとえば、クラス Foo1 を継承してクラス Foo2 を作成しました。クラス Foo1

にはメソッド bar が定義されています。クラス Foo2 にメソッド bar は定義さ

れていませんが、Foo2 のオブジェクトに対して bar を呼び出すと、親クラス

Foo1 のメソッド bar が実行されるのです。メソッドの選択は次のように行われ

ます。まず、オブジェクトが属するクラス Foo2 にメソッド bar が定義されて

いるか調べます。ところが、Foo2 には bar が定義されていないので、スーパー

クラスである Foo1 に bar が定義されているか調べます。ここでメソッド bar

が見つかり、それを実行するのです。このように、メソッドが見つかるまで順番

にスーパークラスを調べていきますが、最上位のスーパークラスまで調べてもメ

ソッドが見つからない場合は、エラーとなります。

 ところで、継承したクラスのメソッドとは違う働きをさせたい場合はどうする

のでしょうか。これはとても簡単で、同名のメソッドを定義することで、そのク

ラスのメソッドを設定することができます。この機能を「オーバーライド(over

ride)」といいます。メソッドを選択する仕組みから見た場合、オーバーライド

は必然の動作です。メソッドはサブクラスからスーパークラスに向かって検索さ

れるので、スーパークラスのメソッドよリサブクラスのメソッドが先に選択され

るのは当然なことなのです。


○Perl のオブジェクト指向

 さて、一般的な話はここまでにして、Perl のオブジェクト指向に目を向けて

みましょう。 Perl の場合、クラスの定義やインスタンスを生み出すための特別

の構文は用意されていません。クラスは「パッケージ(package)」で定義し、パッ

ケージ内で定義された関数がメソッドとして扱われます。そして、オブジェクト

は、リファレンスが指し示すデータにクラスの印を付けることで表します。つま

り、Perl では配列でもハッシュでもスカラー変数でも、リファレンスで指し示

すことができるものであれば何でも、オブジェクトとして扱うことができるので

す。

 これには筆者も驚きました。C++ や Java ではクラスを定義するためのキー

ワード class があり、CLOS や VTOL ではクラスを定義する関数 defclass があ

ります。そして、new や make-instatnce で生成されるオブジェクトは「インス

タンス」として、他のデータとは区別されるのが普通[*2]です。逆に言えば、プ

ログラミング言語によってオブジェクトの構造が決められているのですが、Perl

の場合は違います。オブジェクトの構造はプログラマの都合で選択することがで

きるのです。

 まあ、なんでもありの Perl らしいオブジェクト指向といえるのですが、初心

者の方にはプログラミングの自由度が高すぎて、どうやって使ったらよいか戸惑

うことにもなりかねません。筆者も Perl 本[1] の「クラスはパッケージに過ぎ

ない」という項目を見た時は、「何でパッケージがクラスなんだ?」と頭の中に

???が渦巻いたものです。パッケージは以前のバージョンから存在する機能で

すが、ようするに、クラス定義はパッケージで代用する、という意味に過ぎなかっ

たなのです。

 このように、Perl のオブジェクト指向は他のオブジェクト指向言語とはちょっ

と変わっているため戸惑うこともあるのですが、実際に使ってみると難しいこと

はありません。「簡単なことは簡単に。難しいことは可能に。」という Perl の

ポリシーは、オブジェクト指向にも貫かれているようです。まずは、パッケージ

本来の使い方を説明しましょう。


note:

 [*2] これは既存の機能にオブジェクト指向を追加したハイブリッド言語の場合
   で、オブジェクト指向の元祖 Smalltalk では、 全てのデータがオブジェ
   クトとして扱われます。


○パッケージとは?

 プログラムを作っていると、以前作った関数と同じ処理が必要になる場合があ

ります。このような場合、皆さんはどうしますか。一番てっとり早い方法はソー

スファイルからその関数をコピーすることですね。ですが、ファイル数が増えて

くると、そのうちに必要な関数がどのファイルに入っているかわからなくなって

しまいます。もちろん find や grep を使って検索してもいいのですが、関数が

必要になるたびに検索とコピーを繰り返すのは面倒ですね。このような場合、自

分で作成した関数をライブラリとしてまとめておくと便利です。もともとパッケー

ジは、ライブラリを作成するために使用される機能だったのです。

 ライブラリの作成で問題になるのが「名前の衝突」です。複数のライブラリを

使う時に、同じ名前の関数や変数が存在すると、そのライブラリは正常に動作し

ないでしょう。この名前の衝突を避けるために package を使います。次の例を

見てください。


 package 'foo';
 $a = 10;

 package 'main';
 $a = 20;

 print $a;    # 20 (main パッケージの $a を表示)
 print $foo::a; # 10 (foo パッケージの $a を表示)


 package は名前が所属するグループ(名前空間:namespace)を指定します。最

初に foo というパッケージを宣言しました。そこで定義される関数や変数名は

パッケージ foo に属します。次に、main というパッケージを宣言しました。一

つのファイルの中で package を複数回使ってもかまいません。そのたびに名前

空間が切り替わります。したがって、2番目の $a はパッケージ main に属する

変数となり、最初の変数 $a と衝突することはありません。

 print で $a の値を表示してみましょう。ここでは main パッケージのままな

ので、$a の値は 20 となります。他のパッケージの変数や関数は、「パッケー

ジ名::名前」のようにアクセスすることができます。したがって、$foo::a は

foo パッケージの変数 $a の値を表示します。foo::$a ではないので注意してく

ださいね。

 Perl の場合、慣例として1つのパッケージには1つのファイルを割り当て、

ファイル名はそのパッケージ名に合わせることになっています。したがって、

package の宣言はファイルの先頭で行われることが普通です。なお、package が

省略された場合、そのファイルは main パッケージとなります。拡張子が .pm

のファイルを「モジュール」と呼び、.pl のファイルを「ライブラリ」と呼びま

す。 Perl 5 ではモジュールを使うのが普通です。ライブラリをロードするには

require を、モジュールをロードするには use を使います。ファイルがロード

される場合、そのファイルは読み込みの成否を示す値(1 or 0)を返すことになっ

ています。つまり、最後に実行される文は return 1; や 1; のようなものでな

ければなりません。ご注意くださいませ。


○次回は?

 Perl のパッケージには、この他にも強力な機能が備わっているのですが、と

りあえず package の宣言により名前空間が切り替わることを理解すれば、オブ

ジェクト指向機能を使うことができるようになります。次回は簡単なプログラム

を作りながら、具体的に Perl のオブジェクト指向を説明していく予定です。お

楽しみに。

               ―参考文献―


 [1] Larry Wall, Tom Christiansen, Randal L. Schwartz 共著「プログラミン
   グPerl」改訂版 オライリー・ジャパン 1997

 [2] Sriram Srinivasan 著「実用Perlプログラミング」オライリー・ジャ
   パン 1998

 [3] 井田昌之 「new はやわかり Java」 共立出版 1997


(EOF)